package com.google.code.csf.aspects;

import com.google.code.csf.ServiceLocator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ServiceProxyLoggingAspect {
    private final Class proxyType;
    private final Logger logger;
    private final Level logLevel;
    private final Level errorLevel;
    private final LoggingFormatter formatter;

    public ServiceProxyLoggingAspect(Class proxyType, LoggingFormatter formatter) {
        this(proxyType, Logger.getLogger(proxyType), Level.INFO, Level.ERROR, formatter);
    }

    public ServiceProxyLoggingAspect(Class proxyType, Logger logger, LoggingFormatter formatter) {
        this(proxyType, logger, Level.INFO, Level.ERROR, formatter);
    }

    public ServiceProxyLoggingAspect(Class proxyType, Logger logger, Level logLevel, Level errorLevel, LoggingFormatter formatter) {
        this.proxyType = proxyType;
        this.logger = logger;
        this.logLevel = logLevel;
        this.errorLevel = errorLevel;
        this.formatter = formatter;
    }

    public Object newServiceInstance(final ServiceLocator serviceLocator) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{proxyType, ServiceLocator.class}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(method.getDeclaringClass() == proxyType) {
                    long startTime = System.currentTimeMillis();
                    try {
                        Object located = serviceLocator.locate(proxyType);
                        Object result = method.invoke(located, args);
                        long endTime = System.currentTimeMillis();
                        logger.log(logLevel, formatter.format(startTime, endTime, proxyType.getName() + "." + method.getName(), args));
                        return result;
                    } catch (Throwable throwable) {
                        long endTime = System.currentTimeMillis();
                        logger.log(errorLevel, formatter.format(startTime, endTime, proxyType.getName() + "." + method.getName(), args), throwable);
                        throw throwable;
                    }
                } else if(method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                } else if(method.getDeclaringClass() == ServiceLocator.class) {
                    return method.invoke(serviceLocator, args);
                } else {
                    throw new UnsupportedOperationException(method.getName() + " is not a valid operation of " + proxyType);
                }
            }
        });
    }
}
